//
//  wiimuSSDP.m
//  testUPNP
//
//  Created by 赵帅 on 14-1-6.
//  Copyright (c) 2014年 umbrella. All rights reserved.
//

#import "WiimuUpnp.h"
#import "AsyncUdpSocket.h"
#import "pthread.h"
#import <SystemConfiguration/SystemConfiguration.h>

#define SEARCH_INTERVAL 2

WiimuUpnp * sharedObj;

@interface WiimuUpnp()
{
    AsyncUdpSocket *listenSocket;//接收其他设备notify
    AsyncUdpSocket *searchSocket;//主动search
    
    enumDeviceType deviceType;//linkplay或wifiaudio
    
    NSMutableArray * deviceArray;//设备列表
    
    NSTimer * searchTimer;//定时搜索
    
    NSLock *theLock;
}

@end

@implementation WiimuUpnp
@synthesize wifiReach;

+(WiimuUpnp*)sharedInstance{
    if (sharedObj == nil) {
        sharedObj = [[super allocWithZone:NULL]init];
        sharedObj.delegates = [NSMutableArray array];
    }
    return sharedObj;
}

+ (id)allocWithZone:(NSZone *)zone{
    return [self sharedInstance];//返回单例
}

- (id)copyWithZone:(NSZone*)zone{
    return self;
}

-(id)init
{
	self = [super init];
	if (self)
	{
        theLock = [[NSLock alloc] init];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name: kReachabilityChangedNotification object: nil];
        
        wifiReach = [Reachability reachabilityForLocalWiFi];
        [wifiReach startNotifier];
        
        [self updateInterfaceWithReachability: wifiReach];
	}
	return self;
}

#pragma mark - public methods
-(void)addDelegate:(NSObject<WiimuUpnpDelegate> *)delegate
{
    [self.delegates addObject:delegate];
}

-(void)beginSSDP:(enumDeviceType)type
{
    deviceType = type;
    deviceArray = [[NSMutableArray alloc] init];
    
    searchTimer = [NSTimer timerWithTimeInterval:SEARCH_INTERVAL target:self selector:@selector(searchTimerMethod) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:searchTimer forMode:NSDefaultRunLoopMode];
    
    [self listen];
}

-(void)stopSSDP
{
    [theLock lock];
    [deviceArray removeAllObjects];
    [theLock unlock];
    
    for(id delegate in self.delegates)
    {
        if([delegate respondsToSelector:@selector(allDevicesDisappear)])
        {
            [delegate performSelectorOnMainThread:@selector(allDevicesDisappear) withObject:nil waitUntilDone:YES];
        }
    }
    
    [searchTimer invalidate];
    searchTimer = nil;
    
    [listenSocket close];
}

-(void)refresh
{
    [theLock lock];
    [deviceArray removeAllObjects];
    [theLock unlock];
    
    for(id delegate in self.delegates)
    {
        if([delegate respondsToSelector:@selector(allDevicesDisappear)])
        {
            [delegate performSelectorOnMainThread:@selector(allDevicesDisappear) withObject:nil waitUntilDone:YES];
        }
    }
    
    [self search];
}

-(void)mirrorTo:(NSString *)identifier generateUrl:(NSString *)mirrorUrl
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSString * baseuri = nil;
        int version = 1;
        NSMutableDictionary * deviceInfo = nil;
        for(deviceInfo in deviceArray)
        {
            if([deviceInfo[@"friendlyName"] isEqualToString:identifier])
            {
                baseuri = deviceInfo[@"baseURL"];
                version = [deviceInfo[@"serialNumber"] intValue];
                break;
            }
        }
        
        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@AVTransport/control",baseuri]];
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
        [request setHTTPMethod:@"POST"];
        
        NSString * mirrorMethod = @"SetAVTransportURI";
        unsigned int uuid = 0;
        if(version == 1)
        {
            mirrorMethod = @"SetAVTransportURI";
            uuid = 0;
        }
        else if(version == 2)//新版本
        {
            mirrorMethod = @"SetMirrorURL";
            uuid = [self getRandomNumber:10000 to:65535];
        }
        
        [deviceInfo setObject:[NSNumber numberWithInt:uuid] forKey:@"uuid"];
        
        [request addValue:[NSString stringWithFormat:@"\"urn:schemas-upnp-org:service:AVTransport:1#%@\"",mirrorMethod] forHTTPHeaderField: @"SOAPACTION"];
        
        NSString *UPNPCP_SOAP_BodyHead = @"<?xml version=\"1.0\" encoding=\"utf-8\"?><s:Envelope s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Body><u:";
        NSString *UPNPCP_SOAP_BodyTail = @"></s:Body></s:Envelope>";
        NSString * ServiceType = @"urn:schemas-upnp-org:service:AVTransport:1";
        NSString * CurrentURIMetaData = @"none";
        
        NSString * bodyStr = [NSString stringWithFormat:@"%@%@ xmlns:u=\"%@\"><InstanceID>%u</InstanceID><CurrentURI>%@</CurrentURI><CurrentURIMetaData>%@</CurrentURIMetaData></u:%@%@",UPNPCP_SOAP_BodyHead,mirrorMethod,ServiceType,uuid,mirrorUrl,CurrentURIMetaData, mirrorMethod,UPNPCP_SOAP_BodyTail];
        
        NSData *data = [bodyStr dataUsingEncoding:NSUTF8StringEncoding];
        [request setHTTPBody:data];
        
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        [NSURLConnection sendAsynchronousRequest:request
                                           queue:queue
                               completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
                                   if (error) {
                                   }else{
                                   }
                               }];
    });
}

-(void)stopMirror:(NSString *)identifier
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        NSString * baseuri = nil;
        int version = 1;
        unsigned int uuid = 0;
        for(NSDictionary * dic in deviceArray)
        {
            if([dic[@"friendlyName"] isEqualToString:identifier])
            {
                baseuri = dic[@"baseURL"];
                version = [dic[@"serialNumber"] intValue];
                uuid = [dic[@"uuid"] intValue];
                break;
            }
        }
        
        if([baseuri length] == 0) return;
        if(version == 2 && uuid == 0) return;//新版本，未开始
        
        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@AVTransport/control",baseuri]];
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
        [request setHTTPMethod:@"POST"];
        
        NSString * mirrorMethod = @"StopMirror";
        
        [request addValue:[NSString stringWithFormat:@"\"urn:schemas-upnp-org:service:AVTransport:1#%@\"",mirrorMethod] forHTTPHeaderField: @"SOAPACTION"];
        
        NSString *UPNPCP_SOAP_BodyHead = @"<?xml version=\"1.0\" encoding=\"utf-8\"?><s:Envelope s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Body><u:";
        NSString *UPNPCP_SOAP_BodyTail = @"></s:Body></s:Envelope>";
        NSString * ServiceType = @"urn:schemas-upnp-org:service:AVTransport:1";
        
        NSString * bodyStr = [NSString stringWithFormat:@"%@Stop xmlns:u=\"%@\"><InstanceID>%u</InstanceID></u:Stop%@",UPNPCP_SOAP_BodyHead,ServiceType,uuid,UPNPCP_SOAP_BodyTail];
        
        NSData *data = [bodyStr dataUsingEncoding:NSUTF8StringEncoding];
        [request setHTTPBody:data];
        
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        [NSURLConnection sendAsynchronousRequest:request
                                           queue:queue
                               completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
                                   if (error) {
                                   }else{
                                   }
                               }];
    });
}

#pragma mark - ssdp callback methods
- (BOOL)onUdpSocket:(AsyncUdpSocket *)sock
     didReceiveData:(NSData *)data
            withTag:(long)tag
           fromHost:(NSString *)host
               port:(UInt16)port{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [self messageAreComing:data socket:sock];
    });
    
    return YES;
}

- (void)onUdpSocket:(AsyncUdpSocket *)sock didNotReceiveDataWithTag:(long)tag dueToError:(NSError *)erro
{
    NSLog(@"error tag: %ld",tag);
}

#pragma mark - private methods
-(void)messageAreComing:(NSData *)data socket:(AsyncUdpSocket *)sock
{
    NSString * string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
//    NSLog(@"string:%@",string);
    
    NSArray * ssdpArr = [string componentsSeparatedByString:@"\r\n"];
//    NSLog(@"ssdpArr:%@",ssdpArr);
    
    NSString * ssdpType = [ssdpArr objectAtIndex:0];
//    NSLog(@"ssdpType:%@",ssdpType);
    
    if(!([ssdpType isEqualToString:@"HTTP/1.1 200 OK"] || [ssdpType isEqualToString:@"NOTIFY * HTTP/1.1"]))
    {
        return;
    }
    //必须放主线程 orz
    dispatch_async(dispatch_get_main_queue(), ^{
        [sock receiveWithTimeout:-1 tag:1];
    });
    
//        NSLog(@"received data: %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
    
    //video 判断
    if(deviceType == devLinkPlay)
    {
        for(NSString * str in ssdpArr)
        {
            if([str rangeOfString:@"SERVER"].location != NSNotFound)
            {
                if([str isEqualToString:@"SERVER: Apusone MircoStack UPnP/1.0"])
                {
                    //获取linkplay friendlyname
                    NSString * urlStr = [self getDeviceUrlString:ssdpArr];
//                    NSLog(@"urlStr:%@",urlStr);
                    NSString * xmlStr = [self getDetailXML:urlStr];
//                    NSLog(@"xmlStr:%@",xmlStr);
                   
                    if([urlStr length] == 0 || [[self getXMLContent:xmlStr tag:@"friendlyName"] length] == 0) return;
                    
                    //新老版本linkplay，增加区别设备标识的选项
                    NSMutableDictionary * deviceDic = [NSMutableDictionary dictionaryWithObjectsAndKeys:[self getXMLContent:xmlStr tag:@"friendlyName"],@"friendlyName",urlStr,@"baseURL",[NSDate date],@"timeStamp",[self getXMLContent:xmlStr tag:@"serialNumber"],@"serialNumber", nil];
                    
                    [self dealWithComingDevice:deviceDic];
                }
                break;
            }
        }
        
        return;
    }
    
    //audio
    //检测是否为wifiaudio
    for(NSString * str in ssdpArr)
    {
        if([str rangeOfString:@"SERVER"].location != NSNotFound)
        {
            if(!([str hasPrefix:@"SERVER: Linux/2.6.21"] || [str hasPrefix:@"SERVER: Wiimu AudioRender"]))
            {
                return;
            }
        }
    }
    
    NSString * urlStr = [self getDeviceUrlString:ssdpArr];
//    NSLog(@"urlStr:%@",urlStr);
    NSString * xmlStr = [self getDetailXML:urlStr];
//     NSLog(@"xmlStr:%@",xmlStr);
    NSString * uuid = [self getUUID:urlStr];
//    NSLog(@"urlStr:%@",uuid);

    NSString * requirement = [self getXMLContent:xmlStr tag:@"manufacturerURL"];
    
//     NSLog(@"requirement:%@",requirement);
    
    if([requirement isEqualToString:@"http://www.wiimu.com/"])
    {
        if([urlStr length] == 0 || [[self getXMLContent:xmlStr tag:@"friendlyName"] length] == 0) return;
        
        NSMutableDictionary * deviceDic = [NSMutableDictionary dictionaryWithObjectsAndKeys:uuid?uuid:@"",@"uuid",[self getXMLContent:xmlStr tag:@"friendlyName"],@"friendlyName",urlStr,@"baseURL",[NSDate date],@"timeStamp", nil];
        [self dealWithComingDevice:deviceDic];
    }
}

-(void)search
{
    NSString * ssdpSearchStr = @"M-SEARCH * HTTP/1.1\r\nMX: 3\r\nST: upnp:rootdevice\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\n\r\n";
    searchSocket = [[AsyncUdpSocket alloc] initWithDelegate:self];
    
    NSError *socketError = nil;
    
    if (![searchSocket bindToPort:0 error:&socketError]) {
        NSLog(@"Failed binding socket: %@", [socketError localizedDescription]);
    }
    
    if(![searchSocket joinMulticastGroup:@"239.255.255.250" error:&socketError]){
        NSLog(@"Failed joining multicast group: %@", [socketError localizedDescription]);
    }
    
    if (![searchSocket enableBroadcast:TRUE error:&socketError]){
        NSLog(@"Failed enabling broadcast: %@", [socketError localizedDescription]);
    }
    
    [searchSocket sendData:[ssdpSearchStr dataUsingEncoding:NSUTF8StringEncoding]
                    toHost:@"239.255.255.250"
                      port:1900
               withTimeout:10
                       tag:1];
    
    [searchSocket receiveWithTimeout:10 tag:2];
    [NSTimer scheduledTimerWithTimeInterval:11
                                     target:self
                                   selector:@selector(completeSearch:)
                                   userInfo:self
                                    repeats:NO];
    
//    [searchSocket closeAfterSendingAndReceiving];
}

-(NSString *)getDeviceUrlString:(NSArray *)arr
{
    for(NSString * str in arr)
    {
        NSString * upperStr = [str uppercaseString];
        if([upperStr rangeOfString:@"LOCATION:"].location != NSNotFound)
        {
            NSMutableString * retStr = [str mutableCopy];
            [retStr deleteCharactersInRange:NSMakeRange(0, [@"LOCATION:" length])];
            if([retStr hasPrefix:@" "])
            {
                [retStr deleteCharactersInRange:NSMakeRange(0, 1)];
            }
//            NSLog(@"retStr:%@",str);
            return retStr;
        }
    }
    
    return nil;
}

-(void)searchTimerMethod
{
    if([wifiReach currentReachabilityStatus] != ReachableViaWiFi)
    {
        return;
    }
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self checkIfTooLong];
    });
    [self search];
}

// 从设备获取xml
-(NSString *)getDetailXML:(NSString *)urlStr
{
    NSURL *url = [NSURL URLWithString:urlStr];
    
    if(url == nil) return nil;
    
    NSURLRequest *request = [[NSURLRequest alloc]initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
    NSData *received = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
    NSString *xmlStr = [[NSString alloc]initWithData:received encoding:NSUTF8StringEncoding];
    
    return xmlStr;
}

//从设备获取UUID
-(NSString *)getUUID:(NSString *)urlStr
{
    NSURL *url = [NSURL URLWithString:urlStr];
    
    if(url == nil) return nil;
   // @"http://%@/httpapi.asp?command=getStatus（nie）  @"http://%@/info.html"）（wiimu）
    NSURLRequest *request = [[NSURLRequest alloc]initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@/info.html",url.host]] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
    NSData *received = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
    
    NSDictionary * dic;
//    NSLog(@"dic-------:%@",dic);
    if (received == nil)
    {
        
    }else
    {
        dic = [self dataToArrayOrNSDictionary:received];
    }
    
//    received = nil;
//    NSString *xmlStr = [[NSString alloc]initWithData:received encoding:NSUTF8StringEncoding];
//
//    NSLog(@"dic:%@",dic);
    return dic[@"uuid"];
}

-(NSString *)getXMLContent:(NSString *)xmlString tag:(NSString *)tag
{
    int beginIndex = [xmlString rangeOfString:[NSString stringWithFormat:@"<%@>",tag]].location;
    int endIndex = [xmlString rangeOfString:[NSString stringWithFormat:@"</%@>",tag]].location;
    if(beginIndex == NSNotFound || endIndex == NSNotFound || endIndex <= beginIndex)
    {
        return @"";
    }
    
    NSRange range;
    range.location = beginIndex+[[NSString stringWithFormat:@"<%@>",tag] length];
    range.length = endIndex-range.location;
    
    if(range.length<=0) return @"";
    NSString * returnStr = [xmlString substringWithRange:range];
    
    return returnStr;
}

-(void)listen
{
    listenSocket=[[AsyncUdpSocket alloc] initWithDelegate:self];
    
    NSError *error = nil;
    [listenSocket bindToPort:1900 error:&error];
    [listenSocket enableBroadcast:YES error:&error];
    [listenSocket joinMulticastGroup:@"239.255.255.250" error:&error];
    
    if (error) {
        NSLog(@"error: %@",error);
    }
    
    [listenSocket receiveWithTimeout:-1 tag:1];
}

- (void)completeSearch:(NSTimer *)t
{
    //    NSLog(@"Search Time Ended.");
    [searchSocket close];
    searchSocket = nil;
}

-(void)dealWithComingDevice:(NSDictionary *)device
{
    [theLock lock];
    BOOL isExsit = NO;
//    NSLog(@"device:%@",device);
    for(int i=0;i<[deviceArray count]; i++)
    {
        NSMutableDictionary * dic = [deviceArray objectAtIndex:i];
        if([device[@"uuid"] isEqualToString:dic[@"uuid"]])
        {
            isExsit = YES;
            
            //ip改变
            if(![device[@"baseURL"] isEqualToString:dic[@"baseURL"]])
            {
                [deviceArray replaceObjectAtIndex:i withObject:device];
                
//                NSLog(@"device info change:%@,%@",device[@"friendlyName"],device[@"baseURL"]);
                
                for(id delegate in self.delegates)
                {
                    if([delegate respondsToSelector:@selector(deviceInfoChange:)])
                    {
                        [delegate performSelectorOnMainThread:@selector(deviceInfoChange:) withObject:device waitUntilDone:YES];
                    }
                }
            }
            else
            {
                //即使无改变也更新，主要是时间戳
                for(NSString * keyStr in [device allKeys])
                {
                    [dic setObject:device[keyStr] forKey:keyStr];
                }
            }
            
            break;
        }
    }
    
    if(isExsit == NO)
    {
        [deviceArray addObject:device];
        
//        NSLog(@"new device come:%@",device[@"friendlyName"]);

        for(id delegate in self.delegates)
        {
            if([delegate respondsToSelector:@selector(newDeviceCome:)])
            {
                [delegate performSelectorOnMainThread:@selector(newDeviceCome:) withObject:device waitUntilDone:YES];
            }
        }
    }
    [theLock unlock];
}

-(BOOL)checkIfTooLong
{
    [theLock lock];

    NSMutableArray * deleteArray = [NSMutableArray array];
    
    for(NSDictionary * dic in deviceArray)
    {
        NSDate * date = dic[@"timeStamp"];
        int interval = (int)[date timeIntervalSinceNow];
        
        if(abs(interval) >= SEARCH_INTERVAL*5)
        {
            for(id delegate in self.delegates)
            {
                if([delegate respondsToSelector:@selector(deviceDisappear:)])
                {
                    [delegate performSelectorOnMainThread:@selector(deviceDisappear:) withObject:dic waitUntilDone:YES];
                }
            }
            
            [deleteArray addObject:dic];
        }
    }
    
    for(NSDictionary * dic in deleteArray)
    {
        NSLog(@"remove device:%@",dic[@"friendlyName"]);
        [deviceArray removeObject:dic];
    }

    [theLock unlock];
    return NO;
}

-(int)getRandomNumber:(int)from to:(int)to
{
    return (int)(from + (arc4random() % (to - from + 1)));
}

- (id)dataToArrayOrNSDictionary:(NSData *)jsonData{
    NSError *error = nil;
    id jsonObject = [NSJSONSerialization JSONObjectWithData:jsonData
                                                    options:NSJSONReadingAllowFragments
                                                      error:&error];
    
    if (jsonObject != nil && error == nil){
        return jsonObject;
    }else{
        // 解析错误
        return nil;
    }
}

#pragma mark - wifi status
- (void) reachabilityChanged: (NSNotification* )note
{
    Reachability* curReach = [note object];
    [self updateInterfaceWithReachability: curReach];
}

//处理连接改变后的情况
- (void) updateInterfaceWithReachability:(Reachability*)curReach
{
    //对连接改变做出响应的处理动作。
    NetworkStatus status = [curReach currentReachabilityStatus];

    for(id delegate in self.delegates)
    {
        if([delegate respondsToSelector:@selector(netWorkStatusChanged:)])
        {
            [delegate performSelectorOnMainThread:@selector(netWorkStatusChanged:) withObject:curReach waitUntilDone:YES];
        }
    }
    
    if(status != ReachableViaWiFi)
    {
        [theLock lock];
        [deviceArray removeAllObjects];
        [theLock unlock];

        for(id delegate in self.delegates)
        {
            if([delegate respondsToSelector:@selector(allDevicesDisappear)])
            {
                [delegate performSelectorOnMainThread:@selector(allDevicesDisappear) withObject:nil waitUntilDone:YES];
            }
        }
    }
    else
    {
        [self refresh];
    }
}

@end
